using System;
using System.Collections.Generic;
using System.Text;

using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace Uniriotec.DC.RTSP
{
    public class Client
    {
        private const bool DebugTimeStamp = false;

        #region Private Fields

        private static int RTSP_server_port;
        private static String ServerHost;
        private int receivedFrames = 0;
        private int lostFrames = 0;
        private int lastFrameReceived = 0;
        private long bytesReceived = 0;


        private byte[] rcvdp; //UDP packet received from the server
        private UdpClient RTPsocket; //socket to be used to send and receive UDP packets
        private int RTP_RCV_PORT = 25000; //port where the client will receive the RTP packets
        private byte[] buf; //buffer used to store data received from the server 
        private int FRAME_PERIOD = 100; //Frame period of the video to stream, in ms
        private int VIDEO_LENGTH = 500; //length of the video in frames

        private RtspStates connectionState;

        private Thread receiveVideoThread;
        private Socket RTSPsocket; //socket used to send/receive RTSP messages

        //input and output stream filters

        private BufferedReader<NetworkStream> RTSPBufferedReader;
        private BufferedWriter<NetworkStream> RTSPBufferedWriter;

        private String videoFileName; //video file to request to the server


        private int RTSPSeqNb = 0; //Sequence number of RTSP messages within the session
        private int RTSPid = 0; //ID of the RTSP session (given by the RTSP Server)

        static String CRLF = "\r\n";

        #endregion

        #region Properties

        public int LastFrameReceived
        {
            get { return lastFrameReceived; }
        }

        public int ReceivedFrames
        {
            get { return receivedFrames; }
        }

        public int LostFrames
        {
            get { return lostFrames; }
        }

        public int RTP_RCV_PORT1
        {
            get { return RTP_RCV_PORT; }
            set { RTP_RCV_PORT = value; }
        }

        public RtspStates RtspConnectionState
        {
            get
            {
                return connectionState;
            }
            set
            {
                connectionState = value;

                #region raise ConnectionStateChanged Event

                try
                {
                    EventHandler handler = ConnectionStateChanged;

                    if (handler != null)
                    {
                        handler(this, null);
                    }
                }
                catch
                {
                    // Handle exceptions here
                }

                #endregion


            }
        }

        public String VideoFileName
        {
            get { return videoFileName; }
            set { videoFileName = value; }
        }

        public String RequestVideoURL
        {
            get
            {
                return @"rtsp://" + ServerHost + ":" + RTSP_server_port + "/" + videoFileName;
            }
        }

        #endregion

        #region Events

        public event EventHandler ConnectionStateChanged;
        public event EventHandler<RTPPacketReceivedEventArgs> RTPPacketReceived;

        #endregion

        public Client()
        {
            InitializeVideoThread();
            //allocate enough memory for the buffer used to receive data from the server
            buf = new byte[15000];
        }

        void InitializeVideoThread()
        {
            receiveVideoThread = new Thread(new ThreadStart(backgroundWorker1_DoWork));
        }

        public void PrepareToReceiveVideo()
        {
            //construct a new DatagramSocket to receive RTP packets from the server, on port RTP_RCV_PORT
            if (RTPsocket == null)
                RTPsocket = new UdpClient(RTP_RCV_PORT);

        }
        //------------------------------------
        //Parse Server Response
        //------------------------------------
        public int parse_server_response()
        {
            int reply_code = 0;

            try
            {
                //parse status line and extract the reply_code:
                String StatusLine = RTSPBufferedReader.ReadLine();
                if (StatusLine == null)
                    return -1;
                string[] tokens = StatusLine.Split(' ');

                reply_code = int.Parse(tokens[1]);

                //if reply code is OK get and print the 2 other lines
                if (reply_code == 200)
                {
                    String SeqNumLine = RTSPBufferedReader.ReadLine();
                    String SessionLine = RTSPBufferedReader.ReadLine();

                    //if state == INIT gets the Session Id from the SessionLine
                    tokens = SessionLine.Split(' ');

                    RTSPid = int.Parse(tokens[1]);

                    RTSPBufferedReader.ReadLine(); // throw the final \n out
                }
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine("Exception caught : " + ex);
            }

            return (reply_code);
        }

        public void StartReceivingVideo()
        {
            //start the timer
            if (receiveVideoThread.ThreadState == ThreadState.Unstarted)
                receiveVideoThread.Start();
            else if (receiveVideoThread.ThreadState == ThreadState.Suspended)
                receiveVideoThread.Resume();
            else
            {
                InitializeVideoThread();
                receiveVideoThread.Start();
            }
        }

        //------------------------------------
        //Send RTSP Request
        //------------------------------------

        public void send_RTSP_request(RTSPAction request_type)
        {
            send_RTSP_request(RTSPHelper.GetActionText(request_type));
        }

        public void send_RTSP_request(String request_type)
        {
            send_RTSP_request(request_type, -1);
        }

        public void send_RTSP_request(RTSPAction request_type, int time)
        {
            send_RTSP_request(RTSPHelper.GetActionText(request_type), time);
        }

        /// <summary>
        /// Send a RTSP Request to the server
        /// </summary>
        /// <param name="request_type">The request command - PLAY, SETUP, TEARDOWN, etc</param>
        /// <param name="time">The time to start playing on PLAY request_type</param>
        public void send_RTSP_request(String request_type, int time)
        {
            //increase RTSP sequence number
            RTSPSeqNb++;

            try
            {
                if (RTSPsocket == null)
                    throw new Exception("No connection");

                //Use the RTSPBufferedWriter to write to the RTSP socket

                string command = String.Empty;
                //write the request line:
                command = request_type + " " + RequestVideoURL + " RTSP/1.0" + CRLF;
                RTSPBufferedWriter.Write(command);
                //write the CSeq line: 
                command = "CSeq: " + RTSPSeqNb + CRLF;
                RTSPBufferedWriter.Write(command);

                //check if request_type is equal to "SETUP" and in this case write the Transport: line advertising to the server the port used to receive the RTP packets RTP_RCV_PORT
                if (request_type == "SETUP")
                    command = "Transport: RTP/UDP; client_port= " + RTP_RCV_PORT + CRLF;
                else
                    command = "Session: " + RTSPid + "\n";

                RTSPBufferedWriter.Write(command);

                if (request_type == "PLAY" && time != -1)
                {
                    string timerange = "Range: npt =" + time + "-" + CRLF;
                    RTSPBufferedWriter.Write(timerange);
                    lastFrameReceived = time * 10;
                }

                RTSPBufferedWriter.Write("\n");

                RTSPBufferedWriter.Flush();
            }
            catch (Exception ex)
            {
                Console.Out.WriteLine("Exception caught : " + ex);
                return;
            }
        }

        private void backgroundWorker1_DoWork()
        {
            bool process = true;
            DateTime startTime = DateTime.Now;
            receivedFrames = 0;
            lostFrames = 0;
            bytesReceived = 0;

            while (process)
            {
                try
                {
                    //receive the DP from the socket :
                    IPEndPoint server = new IPEndPoint(IPHelper.GetIP(ServerHost), RTSP_server_port);

                    rcvdp = RTPsocket.Receive(ref server);

                    //create an RTPpacket object from the DP
                    RTPpacket rtp_packet = new RTPpacket(rcvdp, rcvdp.Length);

                    int DiffToLastFrame = (rtp_packet.getsequencenumber() - lastFrameReceived) - 1;
                    if (DiffToLastFrame < 0)
                        continue;

                    receivedFrames++;
                    lostFrames += DiffToLastFrame;
                    lastFrameReceived = rtp_packet.getsequencenumber();
                    bytesReceived += rcvdp.LongLength;

                    //print important header fields of the RTP packet received: 
                    TimeSpan time = DateTime.Now - startTime;

                    if (!DebugTimeStamp)
                        Console.Out.WriteLine("#Rcvd: " + ReceivedFrames + " Lost: " + LostFrames +
                                          " Last: " + LastFrameReceived + " Acum Bytes: " +
                                          bytesReceived + " Bytes: " + rcvdp.LongLength + " Tax: " +
                                          String.Format("{0:f}", (bytesReceived / 1024) / time.TotalSeconds)
                                          + " kB/s");

                    if (DebugTimeStamp)
                        Console.Out.WriteLine("Got RTP packet with SeqNum # " + rtp_packet.getsequencenumber() +
                                        " TimeStamp " + rtp_packet.gettimestamp() + " ms, of type " + rtp_packet.getpayloadtype());


                    //print header bitstream:
                    rtp_packet.printheader();


                    #region raise RTPPacketReceived Event

                    try
                    {
                        EventHandler<RTPPacketReceivedEventArgs> handler = RTPPacketReceived;

                        if (handler != null)
                        {
                            RTPPacketReceivedEventArgs e = new RTPPacketReceivedEventArgs();
                            e.Packet = rtp_packet;

                            handler(this, e);
                        }
                    }
                    catch
                    {
                        // Handle exceptions here
                    }

                    #endregion




                    //Delay to lose some data
                    Random rnd = new Random();
                    Thread.Sleep(rnd.Next(90)); //needed on local
                }
                catch (IOException ioe)
                {
                    throw ioe;
                }
            }
        }

        public void PauseReceivingVideo()
        {
            //stop the timer
            receiveVideoThread.Abort();
        }

        public void DoTearDown()
        {
            //stop receiving
            receiveVideoThread.Abort();


        }


        //------------------------------------
        //main
        //------------------------------------
        public void Connect(IPEndPoint remoteEndPoint)
        {
            Connect(remoteEndPoint.Address.ToString(), remoteEndPoint.Port);
        }
        public void Connect(string server, int port)
        {
            ServerHost = server;
            RTSP_server_port = port;

            ThreadStart doConnect = new ThreadStart(Connect);
            Thread connectionThread = new Thread(doConnect);
            connectionThread.Name = "ConnectionThread";
            connectionThread.Start();

        }

        public void Connect()
        {

            //Establish a TCP connection with the server to exchange RTSP messages
            //------------------
            try
            {
                RTSPsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                RTSPsocket.Connect(ServerHost, RTSP_server_port);
            }
            catch (SocketException e)
            {
                RtspConnectionState = RtspStates.DISCONNECTED;
                return;
            }

            //Set input and output stream filters:
            RTSPBufferedReader = new BufferedReader<NetworkStream>(RTSPsocket);
            RTSPBufferedWriter = new BufferedWriter<NetworkStream>(RTSPsocket);

            //init RTSP sequence number
            RTSPSeqNb = 1;

            //init RTSP state:
            RtspConnectionState = RtspStates.INIT;
        }


    }




}